// Copyright (c) Microsoft Corporation.  All Rights Reserved.  See License.txt in the project root for license information.

namespace FSharp.Compiler.Scripting.DependencyManager.UnitTests

open System
open System.IO
open System.Reflection
open System.Runtime.InteropServices
open System.Threading

open FSharp.Compiler.Interactive.Shell
open FSharp.Compiler.DependencyManager
open FSharp.Compiler.Diagnostics
open FSharp.DependencyManager.Nuget
open FSharp.Test.ScriptHelpers
open FSharp.Test
open FSharp.Test.Utilities

open Internal.Utilities

open Xunit

module Native =
    [<DllImport("NoneExistentDll")>]
    extern int NoneSuch()

type scriptHost (?langVersion: LangVersion) = inherit FSharpScript(langVersion=defaultArg langVersion LangVersion.Preview)

[<Collection(nameof NotThreadSafeResourceCollection)>]
type DependencyManagerInteractiveTests() =

    let getValue ((value: Result<FsiValue option, exn>), (errors: FSharpDiagnostic[])) =
        if errors.Length > 0 then
            failwith <| sprintf "Evaluation returned %d errors:\r\n\t%s" errors.Length (String.Join("\r\n\t", errors))
        match value with
        | Ok(value) -> value
        | Error ex -> raise ex

    let getErrors ((_value: Result<FsiValue option, exn>), (errors: FSharpDiagnostic[])) =
        errors

    [<FSharp.Test.FactSkipOnSignedBuild>]
    member _.``SmokeTest - #r nuget``() =
        let text = """
#r @"nuget:Newtonsoft.Json, Version=9.0.1"
0"""
        use script = new scriptHost()
        let opt = script.Eval(text) |> getValue
        let value = opt.Value
        Assert.Equal(typeof<int>, value.ReflectionType)
        Assert.Equal(0, value.ReflectionValue :?> int)

    [<FSharp.Test.FactSkipOnSignedBuild>]
    member _.``SmokeTest - #r nuget package not found``() =
        let text = """
#r @"nuget:System.Collections.Immutable.DoesNotExist, version=1.5.0"
0"""
        use script = new scriptHost()
        let _opt, errors = script.Eval(text)
        Assert.Equal(errors.Length, 1)

(*
    [<Theory>]
    [<InlineData("""#r "#i "unknown:Astring" """, """ """)>]
    member _.``syntax produces error messages in FSharp 4.7``(code:string, message: string) =
        use script = new scriptHost()
        let errors = script.Eval(code) |> getErrors
        Assert.Contains(message, errors |> Array.map(fun e -> e.Message))
*)
    static member SdkDirOverrideTestData = [|
        [| None |]
        [| Path.Combine(__SOURCE_DIRECTORY__, "..", "..", ".dotnet", "sdk")
        |> Directory.GetDirectories
        |> Seq.head
        |> Some |]
    |]

    [<Theory>]
    [<MemberData(nameof DependencyManagerInteractiveTests.SdkDirOverrideTestData)>]
    member _.``Use Dependency Manager to resolve dependency FSharp.Data`` (sdkDirOverride: string option) =
        let nativeProbingRoots () = Seq.empty<string>

        use dp = new DependencyProvider(NativeResolutionProbe(nativeProbingRoots), false)
        let reportError =
            let report errorType code message =
                match errorType with
                | ErrorReportType.Error -> printfn "PackageManagementError %d : %s" code message
                | ErrorReportType.Warning -> printfn "PackageManagementWarning %d : %s" code message
            ResolvingErrorReport (report)

        let idm = dp.TryFindDependencyManagerByKey(Seq.empty, "", sdkDirOverride, reportError, "nuget")

        if RuntimeInformation.IsOSPlatform(OSPlatform.Windows) then
            let result = dp.Resolve(idm, ".fsx", [|"r", "FSharp.Data,3.3.3"|], reportError, "net472")
            Assert.Equal(true, result.Success)
            Assert.Equal(1, result.Resolutions |> Seq.length)
            Assert.Equal(1, result.SourceFiles |> Seq.length)
            Assert.Equal(2, result.Roots |> Seq.length)

        let result = dp.Resolve(idm, ".fsx", [|"r", "FSharp.Data,3.3.3"|], reportError, "net10.0")
        Assert.Equal(true, result.Success)
        Assert.Equal(1, result.Resolutions |> Seq.length)
        Assert.Equal(1, result.SourceFiles |> Seq.length)
        Assert.Equal(1, result.Roots |> Seq.length)
        ()

    [<Fact>]
    member _.``Dependency Manager Reports package root for nuget package with no build artifacts``() =

        let nativeProbingRoots () = Seq.empty<string>

        use dp = new DependencyProvider(NativeResolutionProbe(nativeProbingRoots), false)
        let reportError =
            let report errorType code message =
                match errorType with
                | ErrorReportType.Error -> printfn "PackageManagementError %d : %s" code message
                | ErrorReportType.Warning -> printfn "PackageManagementWarning %d : %s" code message
            ResolvingErrorReport (report)

        let idm = dp.TryFindDependencyManagerByKey(Seq.empty, "", None, reportError, "nuget")

        let result = dp.Resolve(idm, ".fsx", [|"r", "Microsoft.Data.Sqlite, 3.1.8"|], reportError, "net10.0")
        Assert.Equal(true, result.Success)
        Assert.True((result.Resolutions |> Seq.length) > 1)
        Assert.Equal(1, result.SourceFiles |> Seq.length)
        Assert.True(Option.isSome(result.Roots |> Seq.tryFind(fun root -> root.EndsWith("microsoft.data.sqlite/3.1.8/"))))
        ()


    [<Fact>]
    member _.``Dependency add with nonexistent package should fail``() =

        let nativeProbingRoots () = Seq.empty<string>

        use dp = new DependencyProvider(NativeResolutionProbe(nativeProbingRoots), false)
        let reportError =
            let report errorType code message =
                match errorType with
                | ErrorReportType.Error -> printfn "PackageManagementError %d : %s" code message
                | ErrorReportType.Warning -> printfn "PackageManagementWarning %d : %s" code message
            ResolvingErrorReport (report)

        let idm = dp.TryFindDependencyManagerByKey(Seq.empty, "", None, reportError, "nuget")

        if RuntimeInformation.IsOSPlatform(OSPlatform.Windows) then
            let result = dp.Resolve(idm, ".fsx", [|"r", "System.Collections.Immutable.DoesNotExist"|], reportError, "net472")
            Assert.Equal(false, result.Success)
            Assert.Equal(0, result.Resolutions |> Seq.length)
            Assert.Equal(0, result.SourceFiles |> Seq.length)
            Assert.Equal(0, result.Roots |> Seq.length)

        let result = dp.Resolve(idm, ".fsx", [|"r", "System.Collections.Immutable.DoesNotExist"|], reportError, "net10.0")
        Assert.Equal(false, result.Success)
        Assert.Equal(0, result.Resolutions |> Seq.length)
        Assert.Equal(0, result.SourceFiles |> Seq.length)
        Assert.Equal(0, result.Roots |> Seq.length)
        ()

    [<Fact>]
    member _.``Multiple Instances of DependencyProvider should be isolated``() =
        let nativeProbingRoots () = Seq.empty<string>

        use dp1 = new DependencyProvider(NativeResolutionProbe(nativeProbingRoots), false)
        let reportError =
            let report errorType code message =
                match errorType with
                | ErrorReportType.Error -> printfn "PackageManagementError %d : %s" code message
                | ErrorReportType.Warning -> printfn "PackageManagementWarning %d : %s" code message
            ResolvingErrorReport (report)

        let idm1 = dp1.TryFindDependencyManagerByKey(Seq.empty, "", None, reportError, "nuget")
        if RuntimeInformation.IsOSPlatform(OSPlatform.Windows) then
            let result1 = dp1.Resolve(idm1, ".fsx", [|"r", "FSharp.Data,3.3.3"|], reportError, "net472")
            Assert.Equal(true, result1.Success)
            Assert.Equal(1, result1.Resolutions |> Seq.length)
            Assert.True((result1.Resolutions |> Seq.head).Contains("/net45/"))
            Assert.Equal(1, result1.SourceFiles |> Seq.length)
            Assert.Equal(2, result1.Roots |> Seq.length)
            Assert.True((result1.Roots |> Seq.head).EndsWith("/fsharp.data/3.3.3/"))
            Assert.True((result1.Roots |> Seq.last).EndsWith("/microsoft.netframework.referenceassemblies/1.0.0/"))

        let result2 = dp1.Resolve(idm1, ".fsx", [|"r", "FSharp.Data,3.3.3"|], reportError, "net10.0")
        Assert.Equal(true, result2.Success)
        Assert.Equal(1, result2.Resolutions |> Seq.length)
        let expected2 = "/netstandard2.0/"
        Assert.True((result2.Resolutions |> Seq.head).Contains(expected2))
        Assert.Equal(1, result2.SourceFiles |> Seq.length)
        Assert.Equal(1, result2.Roots |> Seq.length)
        Assert.True((result2.Roots |> Seq.head).EndsWith("/fsharp.data/3.3.3/"))

        use dp2 = new DependencyProvider(NativeResolutionProbe(nativeProbingRoots), false)
        let idm2 = dp2.TryFindDependencyManagerByKey(Seq.empty, "", None, reportError, "nuget")

        if RuntimeInformation.IsOSPlatform(OSPlatform.Windows) then
            let result3 = dp2.Resolve(idm2, ".fsx", [|"r", "System.Json, Version=4.6.0"|], reportError, "net472")
            Assert.Equal(true, result3.Success)
            Assert.Equal(1, result3.Resolutions |> Seq.length)
            Assert.True((result3.Resolutions |> Seq.head).Contains("/netstandard2.0/"))
            Assert.Equal(1, result3.SourceFiles |> Seq.length)
            Assert.Equal(1, result3.SourceFiles |> Seq.length)
            Assert.True((result3.Roots |> Seq.head).EndsWith("/system.json/4.6.0/"))

        let result4 = dp2.Resolve(idm2, ".fsx", [|"r", "System.Json, Version=4.6.0"|], reportError, "net10.0")
        Assert.Equal(true, result4.Success)
        Assert.Equal(1, result4.Resolutions |> Seq.length)
        let expected4 = "/netstandard2.0/"
        Assert.True((result4.Resolutions |> Seq.head).Contains(expected4))
        Assert.Equal(1, result4.SourceFiles |> Seq.length)
        Assert.Equal(1, result4.Roots |> Seq.length)
        Assert.True((result4.Roots |> Seq.head).EndsWith("/system.json/4.6.0/"))
        ()

    [<Fact>]
    member _.``Nuget Reference package with dependencies we should get package roots and dependent references``() =

        let nativeProbingRoots () = Seq.empty<string>

        use dp1 = new DependencyProvider(NativeResolutionProbe(nativeProbingRoots), false)
        let reportError =
            let report errorType code message =
                match errorType with
                | ErrorReportType.Error -> printfn "PackageManagementError %d : %s" code message
                | ErrorReportType.Warning -> printfn "PackageManagementWarning %d : %s" code message
            ResolvingErrorReport (report)

        let idm1 = dp1.TryFindDependencyManagerByKey(Seq.empty, "", None, reportError, "nuget")

        if RuntimeInformation.IsOSPlatform(OSPlatform.Windows) then
            let result1 = dp1.Resolve(idm1, ".fsx", [|"r", "Microsoft.Extensions.Configuration.Abstractions, 3.1.1"|], reportError, "net472")
            Assert.Equal(true, result1.Success)
            Assert.Equal(6, result1.Resolutions |> Seq.length)
            Assert.True((result1.Resolutions |> Seq.head).Contains("/netstandard2.0/"))
            Assert.Equal(1, result1.SourceFiles |> Seq.length)
            Assert.Equal(7, result1.Roots |> Seq.length)
            Assert.True((result1.Roots |> Seq.head).EndsWith("/microsoft.extensions.configuration.abstractions/3.1.1/"))

        // Netstandard gets fewer dependencies than desktop, because desktop framework doesn't contain assemblies like System.Memory
        // Those assemblies must be delivered by nuget for desktop apps
        let result2 = dp1.Resolve(idm1, ".fsx", [|"r", "Microsoft.Extensions.Configuration.Abstractions, 3.1.1"|], reportError, "net10.0")
        Assert.Equal(true, result2.Success)
        Assert.Equal(2, result2.Resolutions |> Seq.length)
        let expected = "/netcoreapp3.1/"
        Assert.True((result2.Resolutions |> Seq.head).Contains(expected))
        Assert.Equal(1, result2.SourceFiles |> Seq.length)
        Assert.Equal(2, result2.Roots |> Seq.length)
        Assert.True((result2.Roots |> Seq.head).EndsWith("/microsoft.extensions.configuration.abstractions/3.1.1/"))
        ()

/// Native dll resolution is not implemented on desktop
#if NETCOREAPP
    [<Fact(Skip="downloads very large ephemeral packages"); >]
    member _.``Script using TorchSharp``() =
        let text = """
#r "nuget:RestoreSources=https://donsyme.pkgs.visualstudio.com/TorchSharp/_packaging/packages2/nuget/v3/index.json"
#r "nuget:libtorch-cpu,0.3.52118"
#r "nuget:TorchSharp,0.3.52118"

TorchSharp.Tensor.LongTensor.From([| 0L .. 100L |]).Device
"""

        if RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.Windows) then
            use script = new scriptHost()
            let opt = script.Eval(text) |> getValue
            let value = opt.Value
            Assert.Equal(typeof<string>, value.ReflectionType)
            Assert.Equal("cpu", value.ReflectionValue :?> string)
        ()


    [<FSharp.Test.FactSkipOnSignedBuild>]
    member _.``Use Dependency Manager to restore packages with native dependencies, build and run script that depends on the results``() =
        // Skip test on arm64, because there is not an arm64 native library
        if RuntimeInformation.ProcessArchitecture = Architecture.Arm64 then
            ()
        else
        let packagemanagerlines = [|
            "r", "Microsoft.ML,version=1.4.0-preview"
            "r", "Microsoft.ML.AutoML,version=0.16.0-preview"
            "r", "Microsoft.Data.Analysis,version=0.4.0"
        |]

        let reportError =
            let report errorType code message =
                match errorType with
                | ErrorReportType.Error -> printfn "PackageManagementError %d : %s" code message
                | ErrorReportType.Warning -> printfn "PackageManagementWarning %d : %s" code message
            ResolvingErrorReport (report)

        let mutable resolverPackageRoots = Seq.empty<string>
        let mutable resolverReferences = Seq.empty<string>

        let nativeProbingRoots () = resolverPackageRoots
        let assemblyProbingPaths () = resolverReferences

        // Restore packages, Get Reference dll paths and package roots
        let result =
            use dp = new DependencyProvider(AssemblyResolutionProbe(assemblyProbingPaths), NativeResolutionProbe(nativeProbingRoots), false)
            let idm = dp.TryFindDependencyManagerByKey(Seq.empty, "", None, reportError, "nuget")
            dp.Resolve(idm, ".fsx", packagemanagerlines, reportError, "net10.0")

        Assert.True(result.Success, "resolve failed")

        resolverPackageRoots <- result.Roots
        resolverReferences <- result.Resolutions

// Build and execute reference
        let referenceText =
#if DISABLED_DUE_TO_ISSUE_8588
//
// https://github.com/dotnet/fsharp/issues/8588
            // For this test case use Assembly Qualified References
            ("", result.Resolutions)
            ||> Seq.fold(fun acc r ->
                let assemblyName = AssemblyName.GetAssemblyName(r)
                acc + "#r @\"" + assemblyName.FullName + "\"" + Environment.NewLine)
#else
            // use standard #r for now
            ("", result.Resolutions)
            ||> Seq.fold(fun acc r ->
                acc + "#r @\"" + r + "\"" + Environment.NewLine)
#endif

        let code = @"
$(REFERENCES)

open System
open System.IO
open System.Linq
open Microsoft.Data.Analysis

let Shuffle (arr:int[]) =
    let rnd = Random()
    for i in 0 .. arr.Length - 1 do
        let r = i + rnd.Next(arr.Length - i)
        let temp = arr.[r]
        arr.[r] <- arr.[i]
        arr.[i] <- temp
    arr

let housingPath = ""housing.csv""
let housingData = DataFrame.LoadCsv(housingPath)
let randomIndices = (Shuffle(Enumerable.Range(0, (int (housingData.Rows.Count) - 1)).ToArray()))
let testSize = int (float (housingData.Rows.Count) * 0.1)
let trainRows = randomIndices.[testSize..]
let testRows = randomIndices.[..testSize]
let housing_train = housingData.[trainRows]

open Microsoft.ML
open Microsoft.ML.Data
open Microsoft.ML.AutoML

let mlContext = MLContext()
let experiment = mlContext.Auto().CreateRegressionExperiment(maxExperimentTimeInSeconds = 15u)
let result = experiment.Execute(housing_train, labelColumnName = ""median_house_value"")
let details = result.RunDetails
printfn ""%A"" result
123
"
        let scriptText = code.Replace("$(REFERENCES)", referenceText)

        // Use the dependency manager to resolve assemblies and native paths
        use dp = new DependencyProvider(AssemblyResolutionProbe(assemblyProbingPaths), NativeResolutionProbe(nativeProbingRoots), false)

        use script = new FSharpScript()
        let opt = script.Eval(scriptText)  |> getValue
        let value = opt.Value
        Assert.Equal(123, value.ReflectionValue :?> int32)

    [<FSharp.Test.FactSkipOnSignedBuild>]
    member _.``Use NativeResolver to resolve native dlls.``() =
        // Skip test on arm64, because there is not an arm64 native library
        if RuntimeInformation.ProcessArchitecture = Architecture.Arm64 then
            ()
        else
        let packagemanagerlines = [|
            "r", "Microsoft.ML,version=1.4.0-preview"
            "r", "Microsoft.ML.AutoML,version=0.16.0-preview"
            "r", "Microsoft.Data.Analysis,version=0.4.0"
        |]

        let reportError =
            let report errorType code message =
                match errorType with
                | ErrorReportType.Error -> printfn "PackageManagementError %d : %s" code message
                | ErrorReportType.Warning -> printfn "PackageManagementWarning %d : %s" code message
            ResolvingErrorReport (report)

        let mutable resolverPackageRoots = Seq.empty<string>

        let mutable resolverReferences = Seq.empty<string>
        let nativeProbingRoots () = resolverPackageRoots

        // Restore packages, Get Reference dll paths and package roots
        let result =
            use dp = new DependencyProvider(NativeResolutionProbe(nativeProbingRoots), false)
            let idm = dp.TryFindDependencyManagerByKey(Seq.empty, "", None, reportError, "nuget")
            dp.Resolve(idm, ".fsx", packagemanagerlines, reportError, "net10.0")

        Assert.True(result.Success, "resolve failed")

        resolverPackageRoots <- result.Roots
        resolverReferences <- result.Resolutions

        use _nativeDependencyResolver = new NativeDllResolveHandler(NativeResolutionProbe(nativeProbingRoots))

        // Build and execute script
        let referenceText =
            ("", result.Resolutions) ||> Seq.fold(fun acc r -> acc + @"#r @""" + r + "\"" + Environment.NewLine)

        let code = @"
$(REFERENCES)

open System
open System.IO
open System.Linq
open Microsoft.Data.Analysis

let Shuffle (arr:int[]) =
    let rnd = Random()
    for i in 0 .. arr.Length - 1 do
        let r = i + rnd.Next(arr.Length - i)
        let temp = arr.[r]
        arr.[r] <- arr.[i]
        arr.[i] <- temp
    arr

let housingPath = ""housing.csv""
let housingData = DataFrame.LoadCsv(housingPath)
let randomIndices = (Shuffle(Enumerable.Range(0, (int (housingData.Rows.Count) - 1)).ToArray()))
let testSize = int (float (housingData.Rows.Count) * 0.1)
let trainRows = randomIndices.[testSize..]
let testRows = randomIndices.[..testSize]
let housing_train = housingData.[trainRows]

open Microsoft.ML
open Microsoft.ML.Data
open Microsoft.ML.AutoML

let mlContext = MLContext()
let experiment = mlContext.Auto().CreateRegressionExperiment(maxExperimentTimeInSeconds = 15u)
let result = experiment.Execute(housing_train, labelColumnName = ""median_house_value"")
let details = result.RunDetails
printfn ""%A"" result
123
"
        let scriptText = code.Replace("$(REFERENCES)", referenceText)

        use script = new FSharpScript()
        let opt = script.Eval(scriptText)  |> getValue
        let value = opt.Value
        Assert.Equal(123, value.ReflectionValue :?> int32)

    [<FSharp.Test.FactSkipOnSignedBuild>]
    member _.``Use AssemblyResolver to resolve assemblies``() =
        // Skip test on arm64, because there is not an arm64 native library
        if RuntimeInformation.ProcessArchitecture = Architecture.Arm64 then
            ()
        else
        let packagemanagerlines = [|
            "r", "Microsoft.ML,version=1.4.0-preview"
            "r", "Microsoft.ML.AutoML,version=0.16.0-preview"
            "r", "Microsoft.Data.Analysis,version=0.4.0"
        |]

        let reportError =
            let report errorType code message =
                match errorType with
                | ErrorReportType.Error -> printfn "PackageManagementError %d : %s" code message
                | ErrorReportType.Warning -> printfn "PackageManagementWarning %d : %s" code message
            ResolvingErrorReport (report)

        let mutable resolverPackageRoots = Seq.empty<string>
        let mutable resolverReferences = Seq.empty<string>

        let nativeProbingRoots () = resolverPackageRoots
        let assemblyProbingPaths () = resolverReferences

        // Restore packages, Get Reference dll paths and package roots
        let result =
            use dp = new DependencyProvider(NativeResolutionProbe(nativeProbingRoots), false)
            let idm = dp.TryFindDependencyManagerByKey(Seq.empty, "", None, reportError, "nuget")
            dp.Resolve(idm, ".fsx", packagemanagerlines, reportError, "net10.0")

        Assert.True(result.Success, "resolve failed")

        resolverPackageRoots <- result.Roots
        resolverReferences <- result.Resolutions

        use _assemblyResolver = new AssemblyResolveHandler(AssemblyResolutionProbe(assemblyProbingPaths))

        // Build and execute script
        let referenceText =
            ("", result.Resolutions)
            ||> Seq.fold(fun acc r ->
                acc + "    @\"" + r + "\";" + Environment.NewLine)

        let code = """
open System.Reflection

let x = [|
$(REFERENCES)
    |]

x |> Seq.iter(fun r ->
    let name = AssemblyName.GetAssemblyName(r)
    let asm = Assembly.Load(name)
    printfn "%A" (asm.FullName)
    )
123
"""
        let scriptText = code.Replace("$(REFERENCES)", referenceText)

        use script = new FSharpScript()
        let opt = script.Eval(scriptText)  |> getValue
        let value = opt.Value
        Assert.Equal(123, value.ReflectionValue :?> int32)

    [<Fact>]
    member _.``Verify that referencing FSharp.Core fails with FSharp Scripts``() =
        let packagemanagerlines = [| "r", "FSharp.Core,version=4.7.1" |]

        let reportError =
            let report errorType code message =
                match errorType with
                | ErrorReportType.Error -> printfn "PackageManagementError %d : %s" code message
                | ErrorReportType.Warning -> printfn "PackageManagementWarning %d : %s" code message
            ResolvingErrorReport (report)

        let mutable resolverPackageRoots = Seq.empty<string>
        let nativeProbingRoots () = resolverPackageRoots

        // Restore packages, Get Reference dll paths and package roots
        let result =
            use dp = new DependencyProvider(NativeResolutionProbe(nativeProbingRoots), false)
            let idm = dp.TryFindDependencyManagerByKey(Seq.empty, "", None, reportError, "nuget")
            dp.Resolve(idm, ".fsx", packagemanagerlines, reportError, "net10.0")

        // Expected: error FS3217: PackageManager cannot reference the System Package 'FSharp.Core'
        Assert.False(result.Success, "resolve succeeded but should have failed")

    [<Fact>]
    member _.``Verify that referencing FSharp.Core succeeds with CSharp Scripts``() =
        let packagemanagerlines = [| "r", "FSharp.Core,version=4.7.1" |]

        let reportError =
            let report errorType code message =
                match errorType with
                | ErrorReportType.Error -> printfn "PackageManagementError %d : %s" code message
                | ErrorReportType.Warning -> printfn "PackageManagementWarning %d : %s" code message
            ResolvingErrorReport (report)

        let mutable resolverPackageRoots = Seq.empty<string>
        let nativeProbingRoots () = resolverPackageRoots

        // Restore packages, Get Reference dll paths and package roots
        let result =
            use dp = new DependencyProvider(NativeResolutionProbe(nativeProbingRoots), false)
            let idm = dp.TryFindDependencyManagerByKey(Seq.empty, "", None, reportError, "nuget")
            dp.Resolve(idm, ".csx", packagemanagerlines, reportError, "net10.0")

        Assert.True(result.Success, "resolve failed but should have succeeded")


    [<Fact>]
    member _.``Verify that Dispose on DependencyProvider unhooks ResolvingUnmanagedDll event handler``() =

        let mutable found = false
        let nativeProbingRoots () =
            found <- true
            Seq.empty<string>

        let reportError =
            let report errorType code message =
                match errorType with
                | ErrorReportType.Error -> printfn "PackageManagementError %d : %s" code message
                | ErrorReportType.Warning -> printfn "PackageManagementWarning %d : %s" code message
            ResolvingErrorReport (report)

        // Set up native resolver to resolve dll's
        do
            use dp = new DependencyProvider(NativeResolutionProbe(nativeProbingRoots), false)

            // Invoking a nonexistent dll via pinvoke cause a probe. which should invoke the call back
            try Native.NoneSuch() |> ignore with _ -> ()
            Assert.True (found, "Failed to invoke the nativeProbingRoots callback")

        // Here the dispose was invoked which should clear the ResolvingUnmanagedDll handler
        found <- false
        try Native.NoneSuch() |> ignore with _ -> ()
        Assert.False (found, "Invoke the nativeProbingRoots callback -- Error the ResolvingUnmanagedDll still fired ")

        use dp = new DependencyProvider(NativeResolutionProbe(nativeProbingRoots))
        let idm = dp.TryFindDependencyManagerByKey(Seq.empty, "", None, reportError, "nuget")

        if RuntimeInformation.IsOSPlatform(OSPlatform.Windows) then
            let result = dp.Resolve(idm, ".fsx", [|"r", "FSharp.Data,3.3.3"|], reportError, "net472")
            Assert.Equal(true, result.Success)
            Assert.Equal(1, result.Resolutions |> Seq.length)
            Assert.Equal(1, result.SourceFiles |> Seq.length)
            Assert.Equal(2, result.Roots |> Seq.length)

        let result = dp.Resolve(idm, ".fsx", [|"r", "FSharp.Data,3.3.3"|], reportError, "net10.0")
        Assert.Equal(true, result.Success)
        Assert.Equal(1, result.Resolutions |> Seq.length)
        Assert.Equal(1, result.SourceFiles |> Seq.length)
        Assert.Equal(1, result.Roots |> Seq.length)
        ()


    [<Fact>]
    member _.``Verify that Dispose on DependencyProvider unhooks ResolvingUnmanagedDll and AssemblyResolver event handler``() =

        let mutable assemblyFound = false
        let assemblyProbingPaths () =
            assemblyFound <- true
            Seq.empty<string>

        let mutable nativeFound = false
        let nativeProbingRoots () =
            nativeFound <- true
            Seq.empty<string>

        // Set up native resolver to resolve dll's
        do
            use dp = new DependencyProvider(AssemblyResolutionProbe(assemblyProbingPaths), NativeResolutionProbe(nativeProbingRoots), false)

            // Invoking a nonexistent dll via pinvoke cause a probe. which should invoke the call back
            try Native.NoneSuch() |> ignore with _ -> ()
            Assert.True (nativeFound, "Failed to invoke the nativeProbingRoots callback")

            // Invoking a nonexistent assembly causes a probe. which should invoke the call back
            try Assembly.Load("NoneSuchAssembly") |> ignore with _ -> ()
            Assert.True (assemblyFound, "Failed to invoke the AssemblyResolve handler")

        // Here the dispose was invoked which should clear the ResolvingUnmanagedDll handler
        nativeFound <- false
        assemblyFound <- false

        try Native.NoneSuch() |> ignore with _ -> ()
        Assert.False (nativeFound, "Invoke the nativeProbingRoots callback -- Error the ResolvingUnmanagedDll still fired ")

        try Assembly.Load("NoneSuchAssembly") |> ignore with _ -> ()
        Assert.False (assemblyFound, "Invoke the assemblyProbingRoots callback -- Error the AssemblyResolve still fired ")
#endif

    [<Fact>]
    member _.``Verify that Dispose on AssemblyResolveHandler unhooks AssemblyResolve event handler``() =

        let mutable assemblyFound = false
        let assemblyProbingPaths () =
            assemblyFound <- true
            Seq.empty<string>

        // Set up AssemblyResolver to resolve dll's
        do
            use dp = new AssemblyResolveHandler(AssemblyResolutionProbe(assemblyProbingPaths))

            // Invoking a nonexistent assembly causes a probe. which should invoke the call back
            try Assembly.Load("NoneSuchAssembly") |> ignore with _ -> ()
            Assert.True (assemblyFound, "Failed to invoke the AssemblyResolve handler")

        // Here the dispose was invoked which should clear the ResolvingUnmanagedDll handler
        assemblyFound <- false

        try Assembly.Load("NoneSuchAssembly") |> ignore with _ -> ()
        Assert.False (assemblyFound, "Invoke the assemblyProbingRoots callback -- Error the AssemblyResolve still fired ")

    [<Fact>]
    member _.``Verify that Dispose cleans up the native paths added``() =
        let nativeProbingRoots () = Seq.empty<string>

        let appendSemiColon (p:string) =
            if not(p.EndsWith(";", StringComparison.OrdinalIgnoreCase)) then
                p + ";"
            else
                p

        let reportError =
            let report errorType code message =
                match errorType with
                | ErrorReportType.Error -> printfn "PackageManagementError %d : %s" code message
                | ErrorReportType.Warning -> printfn "PackageManagementWarning %d : %s" code message
            ResolvingErrorReport (report)

        let mutable initialPath:string = null
        let mutable currentPath:string = null
        let mutable finalPath:string =  null
        do
            initialPath <- appendSemiColon (Environment.GetEnvironmentVariable("PATH"))
            use dp = new DependencyProvider(NativeResolutionProbe(nativeProbingRoots), false)
            let idm = dp.TryFindDependencyManagerByKey(Seq.empty, "", None, reportError, "nuget")
            let mutable currentPath:string = null
            if RuntimeInformation.IsOSPlatform(OSPlatform.Windows) then
                let result = dp.Resolve(idm, ".fsx", [|"r", "Microsoft.Data.Sqlite,3.1.7"|], reportError, "netstandard2.0")
                Assert.Equal(true, result.Success)
                currentPath <-  appendSemiColon (Environment.GetEnvironmentVariable("PATH"))
        finalPath <- appendSemiColon (Environment.GetEnvironmentVariable("PATH"))
        Assert.True(currentPath <> initialPath)     // The path was modified by #r "nuget: ..."
        Assert.Equal(finalPath, initialPath)        // IDispose correctly cleaned up the path

        initialPath <- null
        currentPath <- null
        finalPath <-  null
        do
            initialPath <- appendSemiColon (Environment.GetEnvironmentVariable("PATH"))
            let mutable currentPath:string = null
            use dp = new DependencyProvider(NativeResolutionProbe(nativeProbingRoots), false)
            let idm = dp.TryFindDependencyManagerByKey(Seq.empty, "", None, reportError, "nuget")
            let result = dp.Resolve(idm, ".fsx", [|"r", "Microsoft.Data.Sqlite,3.1.7"|], reportError, "net10.0")
            Assert.Equal(true, result.Success)
            currentPath <-  appendSemiColon (Environment.GetEnvironmentVariable("PATH"))
        finalPath <- appendSemiColon (Environment.GetEnvironmentVariable("PATH"))
        Assert.True(currentPath <> initialPath)      // The path was modified by #r "nuget: ..."
        Assert.Equal(finalPath, initialPath)        // IDispose correctly cleaned up the path

        ()

    [<Fact>]
    member _.``Verify that #help produces help text for fsi + dependency manager``() =
        let expected = """
  F# Interactive directives:

    #r "file.dll";;                               // Reference (dynamically load) the given DLL
    #i "package source uri";;                     // Include package source uri when searching for packages
    #I "path";;                                   // Add the given search path for referenced DLLs
    #load "file.fs" ...;;                         // Load the given file(s) as if compiled and referenced
    #time ["on"|"off"];;                          // Toggle timing on/off
    #help;;                                       // Display help
    #help "idn";;                                 // Display documentation for an identifier, e.g. #help "List.map";;
    #clear;;                                      // Clear screen
    #quit;;                                       // Exit

  F# Interactive command line options:"""

        use script = new FSharpScript(quiet = false, langVersion = LangVersion.V47)

        use capture = new TestConsole.ExecutionCapture()
        let _opt = script.Eval("#help") |> getValue

        let output = capture.OutText

        Assert.Contains(expected, output)

        // this is the end of the line each different platform has a different mechanism for starting fsi
        // Actual output looks similar to: """      See 'testhost --help' for options"""
        Assert.EndsWith("--help' for options", output.Trim())

    [<Fact>]
    member _.``Verify that #help produces help text for fsi + dependency manager language version preview``() =
        let expected = """
  F# Interactive directives:

    #r "file.dll";;                               // Reference (dynamically load) the given DLL
    #i "package source uri";;                     // Include package source uri when searching for packages
    #I "path";;                                   // Add the given search path for referenced DLLs
    #load "file.fs" ...;;                         // Load the given file(s) as if compiled and referenced
    #time ["on"|"off"];;                          // Toggle timing on/off
    #help;;                                       // Display help
    #help "idn";;                                 // Display documentation for an identifier, e.g. #help "List.map";;
    #r "nuget:FSharp.Data, 3.1.2";;               // Load Nuget Package 'FSharp.Data' version '3.1.2'
    #r "nuget:FSharp.Data";;                      // Load Nuget Package 'FSharp.Data' with the highest version
    #clear;;                                      // Clear screen
    #quit;;                                       // Exit

  F# Interactive command line options:"""

        use script = new FSharpScript(quiet = false, langVersion = LangVersion.Preview)

        use capture = new TestConsole.ExecutionCapture()
        let _opt = script.Eval("#help") |> getValue

        let output = capture.OutText

        Assert.Contains(expected, output)

        // this is the end of the line each different platform has a different mechanism for starting fsi
        // Actual output looks similar to: """      See 'testhost --help' for options"""
        Assert.EndsWith("--help' for options", output.Trim())

    [<Fact>]
    member _.``Verify that timeout --- times out and fails``() =
        let nativeProbingRoots () = Seq.empty<string>
        let mutable foundCorrectError = false
        let mutable foundWrongError = false

        use dp = new DependencyProvider(NativeResolutionProbe(nativeProbingRoots), false)
        let reportError =
            let report errorType code message =
                match errorType with
                | ErrorReportType.Error ->
                    if code = 999 then foundCorrectError <- true
                    else foundWrongError <- true
                | ErrorReportType.Warning -> printfn "PackageManagementWarning %d : %s" code message
            ResolvingErrorReport (report)

        let idm = dp.TryFindDependencyManagerByKey(Seq.empty, "", None, reportError, "nuget")
        let result = dp.Resolve(idm, ".fsx", [|"r", "FSharp.Data,3.3.3"|], reportError, "net10.0", timeout=0)           // Fail in 0 milliseconds
        Assert.Equal(false, result.Success)
        Assert.Equal(foundCorrectError, true)
        Assert.Equal(foundWrongError, false)
        ()

    [<Fact>]
    member _.``Verify that script based timeout overrides api based - timeout on script``() =
        let nativeProbingRoots () = Seq.empty<string>
        let mutable foundCorrectError = false
        let mutable foundWrongError = false

        use dp = new DependencyProvider(NativeResolutionProbe(nativeProbingRoots), false)
        let reportError =
            let report errorType code message =
                match errorType with
                | ErrorReportType.Error ->
                    if code = 999 then foundCorrectError <- true
                    else foundWrongError <- true
                | ErrorReportType.Warning -> printfn "PackageManagementWarning %d : %s" code message
            ResolvingErrorReport (report)

        let idm = dp.TryFindDependencyManagerByKey(Seq.empty, "", None, reportError, "nuget")
        let result = dp.Resolve(idm, ".fsx", [|"r", "FSharp.Data,3.3.3"; "r", "timeout=0"|], reportError, "net10.0", null, "", "", "", -1)           // Wait forever
        Assert.Equal(false, result.Success)
        Assert.Equal(foundCorrectError, true)
        Assert.Equal(foundWrongError, false)
        ()

    [<Fact>]
    member _.``Verify that script based timeout overrides api based - timeout on api , forever on script``() =
        let nativeProbingRoots () = Seq.empty<string>
        let mutable foundCorrectError = false
        let mutable foundWrongError = false

        use dp = new DependencyProvider(NativeResolutionProbe(nativeProbingRoots), false)
        let reportError =
            let report errorType code message =
                match errorType with
                | ErrorReportType.Error ->
                    if code = 3401 then foundCorrectError <- true
                    else foundWrongError <- true
                | ErrorReportType.Warning -> printfn "PackageManagementWarning %d : %s" code message
            ResolvingErrorReport (report)

        let idm = dp.TryFindDependencyManagerByKey(Seq.empty, "", None, reportError, "nuget")
        let result = dp.Resolve(idm, ".fsx", [|"r", "FSharp.Data,3.3.3"; "r", "timeout=none"|], reportError, "net10.0", null, "", "", "", -1)           // Wait forever
        Assert.Equal(true, result.Success)
        Assert.Equal(foundCorrectError, false)
        Assert.Equal(foundWrongError, false)
        ()

        
    [<Fact>]
    member _.``Verify that clear cache doesn't fail and clears the cache``() =
        let nativeProbingRoots () = Seq.empty<string>
        let mutable foundCorrectError = false
        let mutable foundWrongError = false

        use dp = new DependencyProvider(NativeResolutionProbe(nativeProbingRoots), true)
        let reportError =
            let report errorType code message =
                match errorType with
                | ErrorReportType.Error ->
                    if code = 999 then foundCorrectError <- true
                    else foundWrongError <- true
                | ErrorReportType.Warning -> printfn "PackageManagementWarning %d : %s" code message
            ResolvingErrorReport (report)

        let idm = dp.TryFindDependencyManagerByKey(Seq.empty, "", None, reportError, "nuget")

        // Resolve and cache the results won't time out
        let _result = dp.Resolve(idm, ".fsx", [|"r", "FSharp.Data,3.3.3"; "r", "timeout=10000"|], reportError, "net10.0", null, "", "", "", -1)           // Wait forever

        // Clear the results
        foundCorrectError <- false
        foundWrongError <- false

        // Now clear the cache --- this will ensure that resolving produces a timeout error.  If we read from the cache the test will fail
        dp.ClearResultsCache(Seq.empty, "", None, reportError)

        let result = dp.Resolve(idm, ".fsx", [|"r", "FSharp.Data,3.3.3"; "r", "timeout=0"|], reportError, "net10.0", null, "", "", "", -1)           // Wait forever
        Assert.Equal(false, result.Success)
        Assert.Equal(foundCorrectError, true)
        Assert.Equal(foundWrongError, false)
        ()
